const typeErrorCodeId = 401; // 401:ErrorCodeId
const syntaxErrorCodeId = 10200002; // 10200002:syntaxErrorCodeId

class BusinessError extends Error {
  code: number;
  constructor(msg: string) {
    super(msg);
    this.name = 'BusinessError';
    this.code = typeErrorCodeId;
  }
}

let MAX_BIT_SIZE: number = 128;
let ASCII_32: number = 32;
let ASCII_127: number = 127;
let g_specialCharForBit: Array<number> = new Array(MAX_BIT_SIZE);
g_specialCharForBit.fill(0);

let g_head: Map<string, number> = new Map<string, number>();
g_head.set("ftp:", 21);
g_head.set("file:", -1);
g_head.set("gopher:", 70);
g_head.set("http:", 80);
g_head.set("https:", 443);
g_head.set("ws:", 80);
g_head.set("wss:", 443);

let g_doubleSegment: string[] = ["..", ".%2e", ".%2E", "%2e.", "%2E.",
  "%2e%2e", "%2E%2E", "%2e%2E", "%2E%2e"];

let g_singlesegment: string[] = [".", "%2e", "%2E"];

let g_spesicalSymbols: string[] = ["@", "%40", "#", "%23", "=", "%3D", ":", "%3A", "/", "%2F", ";", "%3B", "?", "%3F"];

let g_specialcharacter: string[] = ['\0', '\t', '\n', '\r', ' ', '#', '%', '/', ':', '?', '@', '[', '\\', ']'];

function preliminaryWork() {
  let g_specialSymbolsTmp = ['#', '%', '/', ':', '?', '@', '[', '\\', ']', '<', '>', '^', '|'];
  let invalidCharLength = ASCII_32;
  for (let i = 0; i <= invalidCharLength; i++) {
    g_specialCharForBit[i] = 1;
  }
  for (let i = 0; i < g_specialSymbolsTmp.length; i++) {
    g_specialCharForBit[g_specialSymbolsTmp[i].charCodeAt(0)] = 1;
  }
  g_specialCharForBit[ASCII_127] = 1;
}

function checkCharacter(input: string) {
  let inputLen = input.length;
  for (let i = 0; i < inputLen; i++) {
    let charPos: number = input.charCodeAt(i);
    if (charPos >= 0 && charPos < MAX_BIT_SIZE) {
      if (g_specialCharForBit[charPos]) {
        return false;
      }
    }
  }
  return true;
}

function deleteC0OrSpace(input: string): string {
  if (input == "") {
    return "";
  }
  let i: number = 0;
  let strLen = input.length;
  let charCodeOfC0 = '\0'.charCodeAt(0);
  let charCodeOfSpace = ' '.charCodeAt(0);
  while (i < strLen) {
    let inputCharCode = input.charCodeAt(i);
    if (inputCharCode >= charCodeOfC0 && inputCharCode <= charCodeOfSpace) {
      i++;
      continue;
    }
    break;
  }
  let trimHead = input.substring(i);
  strLen = trimHead.length;
  if (strLen == 0) {
    return "";
  }
  for (i = strLen - 1; i >= 0; i--) {
    let inputCharCode = trimHead.charCodeAt(i);
    if (inputCharCode >= charCodeOfC0 && inputCharCode <= charCodeOfSpace) {
      continue;
    }
    break;
  }
  if (i < 0) {
    return "";
  }
  return trimHead.substring(0, i + 1);
}

function deleteTabOrNewline(input: string): string {
  let res = "";
  for (let i = 0; i < input.length; i++) {
    let ch = input[i];
    if (ch == '\t' || ch == '\n' || ch == '\r') {
      continue;
    } else {
      res += ch;
    }
  }
  return res;
}

function isAlpha(char: string): boolean {
  return char.length === 1 && ((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z'));
}

function isNum(char: string): boolean {
  return char.length === 1 && (char >= '0' && char <= '9');
}

function isAlphaNum(ch: string): boolean {
  return isAlpha(ch) || isNum(ch);
}

function isSpecialScheme(scheme: string) {
  return g_head.has(scheme);
}

function isFileNotHost(input: string) {
  if (isAlpha(input[0]) && (input[1] == ':' || input[1] == '|')) {
    return true;
  }
  return false;
}

function isHexDigit(ch: string): boolean {
  return isNum(ch) || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f');
}

function DecodeSpecialChars(input: string): string {
  let temp: string = input;
  let len: number = temp.length;
  if (input.length == 0) {
    return temp;
  }
  let pos: number = temp.indexOf("%");
  while ((pos != -1) && (pos < len - 2)) {
    if (isHexDigit(temp[pos + 1]) && isHexDigit(temp[pos + 2])) {
      let subStr: string = temp.substring(pos + 1, pos + 3);
      let octNum: number = parseInt(subStr, 16);
      if (isNaN(octNum)) {
        return temp;
      }

      let convertedChar = String.fromCharCode(octNum);
      temp = temp.slice(0, pos) + convertedChar + temp.slice(pos + 3);
      len = len - 2;
    }
    pos = temp.indexOf("%", pos + 1);
  }
  return temp;
}

function toHex(num: number): string {
  const hexDigits = "0123456789abcdef";
  let hexStr = "";
  while (num > 0) {
    const remainder = num % 16;
    hexStr = hexDigits[remainder] + hexStr;
    num = Math.floor(num / 16);
  }

  return hexStr || "0";
}

function dealIPv4(input: string): string {
  let temp: Array<string> = new Array<string>();
  let pos: number = input.lastIndexOf(':');
  let index: number = pos;
  let left = pos + 1;
  let hexVal: string = "";
  let val: string = "";
  while (true) {
    pos = input.indexOf(".", left);
    if (pos == -1) {
      break;
    }

    val = input.substring(left, pos);
    let num = parseInt(val, 10);
    if (isNaN(num) && num > 255) {
      return val;
    }

    hexVal = toHex(num);
    if (hexVal.length == 1) {
      hexVal = '0' + hexVal;
    }
    temp.push(hexVal);
    left = pos + 1;
  }
  val = input.substring(left);
  let num = parseInt(val, 10);
  if (isNaN(num) && num > 255) {
    return val;
  }
  hexVal = toHex(num);
  if (hexVal.length == 1) {
    hexVal = '0' + hexVal;
  }
  temp.push(hexVal);
  let res: string = input.substring(0, index);
  res = res + ":" + temp[0] + temp[1] + ":" + temp[2] + temp[3];
  return res;
}

function formatIPv6(input: string): string {
  let pos: number = input.indexOf("::");
  let index: number = pos;
  if (pos != -1) {
    let left: number = 0;
    let count: number = 0;
    while ((pos = input.indexOf(":", left)) != -1) {
      count += 1;
      left = pos + 1;
    }
    let size = 7 - (count - 2);
    let temp = "";
    for (let i = 0; i < size - 1; i++) {
      temp += ":0";
    }
    temp += ":";
    input = input.slice(0, index) + temp + input.substring(index + 2);
    if (index == 0) {
      input = "0" + input;
    }
  }
  return input;
}

function removeLeadingZeros(inputs: Array<string>) {
  let inputSize = inputs.length;
  for (let i = 0; i < inputSize; i++) {
    let strLen: number = inputs[i].length;
    let count: number = 0;
    let j = 0;
    for (j = 0; j < strLen; j++) {
      if (inputs[i][j] != '0') {
        break;
      }
      count++;
    }
    if (count == strLen) {
      inputs[i] = "0";
    } else if (count != 0) {
      inputs[i] = inputs[i].substring(j);
    }
  }
}

function zeroCompression(inputs: Array<string>): string {
  let maxIndex: number = 0;
  let maxSize: number = 0;
  let index: number = 0;
  let size: number = 0;
  let needZeroCompression: boolean = false;
  let inputSize: number = inputs.length;
  for (let i = 0; i < inputSize; i++) {
    index = i;
    size = 0;
    while (i < inputSize && inputs[i] == "0") {
      needZeroCompression = true;
      size++;
      i++;
    }
    if (maxSize < size) {
      maxSize = size;
      maxIndex = index;
    }
  }
  let res: string = "";
  for (let i = 0; i < inputSize; i++) {
    if (needZeroCompression && i == maxIndex) {
      if (maxIndex == 0) {
        res += "::";
      } else {
        res += ":";
      }
      i += maxSize - 1;
      continue;
    }
    res += inputs[i];
    i != (inputSize - 1) ? res += ":" : "";
  }
  return res;
}

function compress(input: string): string {
  let temp: Array<string> = new Array<string>();
  let pos: number = 0;
  let left: number = 0;
  while (true) {
    pos = input.indexOf(":", left);
    if (pos == -1) {
      break;
    }
    temp.push(input.substring(left, pos));
    left = pos + 1;
  }
  temp.push(input.substring(left));
  removeLeadingZeros(temp);
  let res: string = zeroCompression(temp);
  return res.toLowerCase();
}

function isRadix(input: string, radix: string): boolean {
  let len = input.length;
  for (let i = 0; i < len; i++) {
    if (!radix.includes(input[i])) {
      return false;
    }
  }
  return true;
}

function isNumber(num: string, convension: number[]): boolean {
  let len: number = num.length;
  if (len >= 2 && num[0] == '0' && (num[1] == 'x' || num[1] == 'X')) {
    convension[0] = 16;
    let subStr = num.substring(2);
    if (subStr.length == 0) {
      return true;
    }
    return isRadix(subStr, "0123456789abcdefABCDEF");
  } else if (len >= 1 && num[0] == '0') {
    convension[0] = 8;
    let subStr = num.substring(1);
    if (subStr.length == 0) {
      return true;
    }
    return isRadix(subStr, "01234567");
  } else if (isRadix(num, "0123456789")) {
    convension[0] = 10;
    return true;
  }
  return false;
}

function BinaryConversion(num: string, radix: number): string {
  let val: number = 0;
  if (radix == 16) {
    if (num.substring(2).length == 0) {
      return "0";
    }
    val = parseInt(num, 16);
    if (isNaN(val)) {
      console.error("binaryConversion Failed");
      return num;
    }
    return String(val);
  } else if (radix == 8) {
    if (num.substring(1).length == 0) {
      return "0";
    }
    val = parseInt(num, 8);
    if (isNaN(val)) {
      console.error("binaryConversion Failed");
      return num;
    }
    return String(val);
  }
  return num;
}

function IsFormatIPv4(nums: Array<string>): number {
  let len = nums.length;
  for (let i = 0; i < len; i++) {
    if (nums[i].length > 8) {
      return i;
    }
    if (nums[i].length > 0 && (parseInt(nums[i]) > 255)) {
      return i;
    }
  }
  return -1;
}

function SplitNum(num: string, storeNumber: number[]): string {
  if (num.length > 8) {
    storeNumber[0] = num.length;
    return num;
  }
  let val: number = parseInt(num);
  let nums: Array<string> = new Array<string>();
  let res: string = "";
  while (val > 0) {
    let numConvert: number = val % 256;
    nums.push(String(numConvert));
    val = Math.floor(val / 256);
  }
  for (let i = nums.length - 1; i >= 0; i--) {
    res += nums[i] + ".";
  }

  storeNumber[0] = nums.length;
  return res.slice(0, res.length - 1);
}

class UrlInfo {
  c_scheme: string = "";
  c_fragment: string = "";
  c_query: string = "";
  c_path: Array<string> = [];
  c_host: string = "";
  c_isSpecialPath: boolean = false;
  c_username: string = "";
  c_password: string = "";
  c_port: number = -1;
}

class innerURL {
  flags: number[] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
  c_scheme: string = "";
  c_fragment: string = "";
  c_query: string = "";
  c_path: Array<string> = [];
  c_host: string = "";
  c_isSpecialPath: boolean = false;
  c_username: string = "";
  c_password: string = "";
  c_port: number = -1;

  private ipv4Array: Array<string>;

  analysizeScheme(input: string): boolean {
    if (!isAlpha(input[0])) {
      this.flags[0] = 1;
      return false;
    }
    let sizeLen: number = input.length;
    for (let i = 1; i < sizeLen - 1; i++) {
      if (!isAlphaNum(input[i]) && input[i] != '+' && input[i] != '-' && input[i] != '.') {
        this.flags[0] = 1;
        return false;
      }
    }
    this.c_scheme = input.toLowerCase();
    if (isSpecialScheme(this.c_scheme)) {
      this.flags[1] = 1;
    }
    return true;
  }

  analysizeFragment(fragment: string) {
    this.c_fragment = fragment;
    this.flags[8] = 1;
  }

  analysizeQuery(query: string) {
    this.c_query = query;
    this.flags[7] = 1;
  }

  analysizeFilePath(input: string) {
    let temp: Array<string> = new Array<string>();
    let pos: number = 0;
    while ((pos = input.indexOf('/')) != -1 || (pos = input.indexOf('\\')) != -1) {
      temp.push(input.substring(0, pos));
      input = input.substring(pos + 1);
    }
    temp.push(input);
    let tempLength: number = temp.length;
    for (let i = 0; i < tempLength; i++) {
      if (g_doubleSegment.includes(temp[i])) {
        if (this.c_path.length == 1 && isFileNotHost(this.c_path[0]) && this.c_path[0].length == 2) {
          this.c_path[0] = this.c_path[0].charAt(0) + ':';
        } else if (this.c_path.length > 0) {
          this.c_path.pop();
        }
        if (i == temp.length - 1) {
          this.c_path.push("");
        }
        continue;
      }

      if (g_singlesegment.includes(temp[i])) {
        if (i == temp.length - 1) {
          this.c_path.push("");
        }
        continue;
      }
      this.c_path.push(temp[i]);
      this.flags[6] = 1;
    }

    let it = this.c_path[0];
    if (isAlpha(it[0]) && (it[1] == ':' || it[1] == '|')) {
      if (it.length == 2) {
        this.c_path[0] = it[0] + ':';
        this.flags[4] = 0;
        this.c_host = "";
      }
    }
  }

  analysizeSpecialFile(input: string, pos: number) {
    let strHost: string = input.substring(0, pos);
    let strPath: string = input.substring(pos + 1);
    let special: boolean = true;
    let isFile = isFileNotHost(strHost);
    if (!isFile) {
      this.analysizeHost(strHost, special);
    } else if (!isFile && this.flags[0]) {
      return;
    }
    if (!isFile) {
      this.analysizeFilePath(strPath);
    } else {
      this.analysizeFile(input);
    }
  }

  analysizeIPv6Host(input: string) {
    let regex: RegExp = new RegExp("(::|(:((:[0-9A-Fa-f]{1,4}){1,7}))|(([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|:))|(((:(:[0-9A-Fa-f]{1,4}){0,5}:)|(([0-9A-Fa-f]{1,4}:){1}(:[0-9A-Fa-f]{1,4}){0,4}:)|(([0-9A-Fa-f]{1,4}:){2}(:[0-9A-Fa-f]{1,4}){0,3}:)|(([0-9A-Fa-f]{1,4}:){3}(:[0-9A-Fa-f]{1,4}){0,2}:)|(([0-9A-Fa-f]{1,4}:){4}(:[0-9A-Fa-f]{1,4})?:)|(([0-9A-Fa-f]{1,4}:){5}:)|(([0-9A-Fa-f]{1,4}:){6}))((25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)\\.){3}(25[0-5]|2[0-4]\\d|1\\d{2}|[1-9]\\d|\\d)))(%[a-zA-Z0-9._]+)?")
    if (!regex.test(input)) {
      this.flags[0] = 1;
      return;
    }
    let pos: number = input.indexOf('.');
    if (pos != -1) {
      input = dealIPv4(input);
    }
    input = formatIPv6(input);
    input = compress(input);
    this.c_host = "[" + input + "]";
    this.flags[4] = 1;
    this.flags[10] = 1;
  }

  RemovalIPv4(inputStr: string): boolean {
    this.ipv4Array = new Array<string>();
    let pos: number = 0;
    let left: number = 0;
    // let inputs: Array<string> = new Array<string>()
    pos = inputStr.indexOf(".", left);
    while (pos != -1) {
      this.ipv4Array.push(inputStr.substring(left, pos));
      left = pos + 1;
      pos = inputStr.indexOf(".", left);
    }

    if (left != inputStr.length) {
      this.ipv4Array.push(inputStr.substring(left));
    }

    let inputSize: number = this.ipv4Array.length;
    let res: Array<string> = new Array<string>();
    for (let i = 0; i < inputSize; i++) {
      let conversion: number[] = [0];
      if (isNumber(this.ipv4Array[i], conversion)) {
        res.push(BinaryConversion(this.ipv4Array[i], conversion[0]));
      } else {
        return false;
      }
    }
    this.ipv4Array = res;
    let isIPv4 = true;
    for (let i = 0; i < inputSize; i++) {
      if (res[i] == "") {
        isIPv4 = false;
        this.flags[0] = 1;
        if (i == inputSize - 1) {
          res.push("");
          this.flags[0] = 0;
        }
      }
    }
    return isIPv4;
  }

  formatIPv4(nums: Array<string>) {
    let len: number = nums.length;
    let index: number = IsFormatIPv4(nums);
    let res: string = "";
    if (index == -1) {
      for (let i = 0; i < len - 1; i++) {
        res += nums[i] + '.';
      }
      for (let i = 0; i < (4 - len); i++) {
        res += "0.";
      }
      res += nums[len - 1];
      this.c_host = res;
      this.flags[4] = 1;
    } else if (index == (len - 1)) {
      for (let i = 0; i < (len - 1); i++) {
        res += nums[i] + ".";
      }
      let storeNumber: number[] = [0];
      let temp: string = SplitNum(nums[index], storeNumber);
      if (storeNumber[0] + (len - 1) > 4) {
        this.flags[0] = 1;
        return;
      }
      for (let i = 0; i < (4 - (len - 1 + storeNumber[0])); i++) {
        temp = "0." + temp;
      }
      this.c_host = res + temp;
      this.flags[4] = 1;
    } else {
      this.flags[0] = 1;
    }
  }

  analysizeIPv4(input: string) {
    let isIPv4: boolean = false;
    isIPv4 = this.RemovalIPv4(input);
    let temp: Array<string> = this.ipv4Array;
    let tempLen: number = temp.length;
    let lastSize: number = temp[tempLen - 1].length;
    if (isIPv4 && lastSize > 8) {
      this.flags[0] = 1;
      return;
    }
    let res: string = "";
    for (let i = 0; i < tempLen; i++) {
      res += temp[i];
      if (i != (tempLen - 1)) {
        res += '.';
      }
    }
    if (isIPv4) {
      if (tempLen > 4) {
        res = res.toLowerCase();
        this.c_host = res;
        this.flags[4] = 1;
      } else if (tempLen == 4) {
        if (IsFormatIPv4(temp) == -1) {
          this.c_host = res;
          this.flags[4] = 1;
        } else {
          this.flags[0] = 1;
        }
      } else {
        this.formatIPv4(temp);
      }
    } else {
      res = res.toLowerCase();
      this.c_host = res;
      this.flags[4] = 1;
    }
  }

  analysizeOpaqueHost(input: string) {
    let inputSize: number = input.length;
    for (let i = 0; i < inputSize; i++) {
      let ch = input[i];
      let result = g_specialcharacter.indexOf(ch);
      if ((ch != '%') && (result != -1)) {
        this.flags[0] = 1;
        return;
      }
    }
    this.c_host = input;
    this.flags[4] = 1;
  }

  analysizeHost(input: string, special: boolean) {
    if (input.length == 0) {
      this.flags[0] = 1;
      return;
    }
    let inputLength = input.length;
    if (input[0] == '[') {
      if (input[inputLength - 1] == ']') {
        input = input.substring(1, inputLength - 1);
        this.analysizeIPv6Host(input);
        return;
      }
      this.flags[0] = 1;
      return;
    }
    if (!special) {
      this.analysizeOpaqueHost(input);
      return;
    }
    let decodedInput = DecodeSpecialChars(input);
    if (!checkCharacter(decodedInput)) {
      this.flags[0] = 1;
      return;
    }
    this.analysizeIPv4(decodedInput);
  }

  analysizeFile(input: string) {
    let special: boolean = true;
    if ((input[0] == '/' || input[0] == '\\') && (input[1] == '/' || input[1] == '\\')) {
      let temp: string = input.substring(2);
      let pos: number = 0;
      if (((pos = temp.indexOf('/')) != -1 || (pos = temp.indexOf('\\')) != -1) && pos == 0) {
        temp = temp.substring(1);
        this.analysizeFilePath(temp);
      } else if (((pos = temp.indexOf('/')) != -1 || (pos = temp.indexOf('\\')) != -1) && pos != 0) {
        this.analysizeSpecialFile(temp, pos);
      } else {
        if (temp.length != 0 && this.flags[0] == 1) {
          this.analysizeHost(temp, special);
        } else if (temp.length != 0 && this.flags[0] == 0) {
          this.analysizeHost(temp, special);
          return;
        }
      }
    } else {
      if (input[0] == '/' || input[0] == '\\') {
        input = input.substring(1);
      }
      this.analysizeFilePath(input);
    }
  }

  analysizeUsernameAndPasswd(strHost: string): string {
    let pos: number = strHost.length - 1;
    for (; pos >= 0; pos--) {
      if (strHost[pos] == '@') {
        break;
      }
    }
    let userAndPasswd = strHost.substring(0, pos);
    strHost = strHost.substring(pos + 1);
    if (userAndPasswd.length == 0) {
      return strHost;
    }
    if (userAndPasswd.indexOf('@') != -1) {
      while (true) {
        let posTemp: number = userAndPasswd.indexOf('@');
        if (posTemp != -1) {
          userAndPasswd = userAndPasswd.replace('@', "%40");
        } else {
          break;
        }
      }
    }
    if (userAndPasswd.indexOf(':') != -1) {
      let position: number = userAndPasswd.indexOf(':');
      let user: string = userAndPasswd.substring(0, position);
      let keyWord: string = userAndPasswd.substring(position + 1);
      if (user.length != 0) {
        this.c_username = user;
        this.flags[2] = 1;
      }
      if (keyWord.length != 0) {
        this.c_password = keyWord;
        this.flags[3] = 1;
      }
    } else {
      this.c_username = userAndPasswd;
      this.flags[2] = 1;
    }
    return strHost;
  }

  analysizePort(port: string) {
    if (port.length == 0) {
      return;
    }
    for (let i = 0; i < port.length; i++) {
      if (!isNum(port[i])) {
        this.flags[0] = 1;
        return;
      }
    }
    if (port.length >= 6) {
      this.flags[0] = 1;
      return;
    }
    let it: number = parseInt(port);
    const maxPort: number = 65535;
    if (it > maxPort) {
      this.flags[0] = 1;
      return;
    }
    this.flags[5] = 1;
    for (const [key, value] of g_head) {
      if (key == this.c_scheme && value == it) {
        this.c_port = -1;
        this.flags[5] = 0;
        return;
      }
    }
    this.c_port = it;
  }

  analysizePath(path: string, special: boolean) {
    let temp: Array<string> = new Array<string>();
    let pos: number = 0;
    while ((pos = path.indexOf('/')) != -1 || ((pos = path.indexOf('\\')) != -1 && special)) {
      temp.push(path.substring(0, pos));
      path = path.substring(pos + 1);
    }
    temp.push(path);
    let tempLen: number = temp.length;
    for (let it = 0; it < tempLen; it++) {
      let result: number = g_doubleSegment.indexOf(temp[it]);
      if (result != -1) {
        if (this.c_path.length == 0 && it == (tempLen - 1)) {
          this.c_path.push("");
          this.flags[6] = 1;
        }
        if (this.c_path.length == 0) {
          continue;
        }
        this.c_path.pop();
        if (it == (tempLen - 1)) {
          this.c_path.push("");
          this.flags[6] = 1;
        }
        continue;
      }

      result = g_singlesegment.indexOf(temp[it]);
      if (result != -1 && it == (tempLen - 1)) {
        this.c_path.push("");
        this.flags[6] = 1;
        continue;
      }
      if (result == -1) {
        this.c_path.push(temp[it]);
        this.flags[6] = 1;
      }
    }
  }

  parsingHostAndPath(input: string, pos: number) {
    let special: boolean = true;
    let inputLen: number = input.length;
    for (pos = 0; pos < inputLen; pos++) {
      if (input[pos] == '/' || input[pos] == '\\') {
        break;
      }
    }
    let strHost: string = input.substring(0, pos);
    let strPath: string = input.substring(pos + 1);
    if (strHost.indexOf('@') != -1) {
      strHost = this.analysizeUsernameAndPasswd(strHost);
    }
    if (strHost.length == 0) {
      this.flags[0] = 1;
      return;
    }
    if (strHost[strHost.length - 1] != ']' && (strHost.lastIndexOf(':') != -1)) {
      pos = strHost.lastIndexOf(':');
      if (pos != -1) {
        let port: string = strHost.substring(pos + 1);
        strHost = strHost.substring(0, pos);
        this.analysizePort(port);
      }
    }
    if (strHost[strHost.length - 1] != ']' && strHost.lastIndexOf(':') != -1 && this.flags[0] == 1) {
      return;
    }
    this.analysizeHost(strHost, special);
    this.analysizePath(strPath, special);
  }

  analysizeOnlyHost(input: string, pos: number) {
    let strHost: string = input;
    if (strHost.indexOf('@') != -1) {
      strHost = this.analysizeUsernameAndPasswd(strHost);
    }
    if (strHost.length == 0) {
      this.flags[0] = 1;
      return;
    }
    if (strHost[strHost.length - 1] != ']') {
      if ((pos = strHost.lastIndexOf(':')) != -1) {
        let port: string = strHost.substring(pos + 1);
        strHost = strHost.substring(0, pos);
        this.analysizePort(port);
      }
      if ((pos = strHost.lastIndexOf(':')) != -1 && this.flags[0] == 1) {
        return;
      }
    }
    this.analysizeHost(strHost, true);
  }

  analysizeFilescheme(input: string) {
    let strPath: string = this.c_scheme + input;
    this.c_scheme = "file:";
    this.flags[1] = 1;
    this.analysizeFilePath(strPath);
  }

  analysizeStrHost(input: string): string {
    if (input.indexOf('@') != -1) {
      input = this.analysizeUsernameAndPasswd(input);
    }
    if (input.length == 0) {
      this.flags[0] = 1;
    }
    return input;
  }

  analysizeHostPath(input: string): string {
    let pos: number = 0;
    if (input[input.length - 1] != ']' && (pos = input.lastIndexOf(':')) != -1) {
      let port: string = input.substring(pos + 1);
      input = input.substring(0, pos);
      this.analysizePort(port);
      if (this.flags[0] == 1) {
        return input;
      }
    }
    return input;
  }

  analysizeInfoPath(input: string) {
    this.flags[9] = 1;
    if (this.c_path.length == 0) {
      this.c_path.push("");
    }
    this.c_path[0] = input;
    this.flags[6] = 1;
    return;
  }

  analysizeNoDefaultProtocol(input: string) {
    if (this.c_scheme.length == 2) {
      this.analysizeFilescheme(input);
      return;
    }
    if (input[0] == '/' && input[1] == '/' && input[2] != '/') {
      let hostAndPath: string = input.substring(2);
      if (hostAndPath.length == 0) {
        return;
      }
      let i: number = 0;
      let special: boolean = false;
      let strHost: string = "";
      if (hostAndPath.indexOf('/') != -1) {
        i = hostAndPath.indexOf('/');
        strHost = hostAndPath.substring(0, i);
        let strPath: string = hostAndPath.substring(i + 1);
        if (strHost.indexOf('@') != -1) {
          strHost = this.analysizeUsernameAndPasswd(strHost);
        }
        if (strHost.length == 0) {
          this.flags[0] = 1;
          return;
        }
        let pos: number = 0;
        if (strHost[strHost.length - 1] != ']' && (pos = strHost.lastIndexOf(':')) != -1) {
          let port: string = strHost.substring(pos + 1);
          strHost = strHost.substring(0, pos);
          this.analysizePort(port);
        }
        if (strHost[strHost.length - 1] != ']' && (pos = strHost.lastIndexOf(':')) != -1 && this.flags[0] == 1) {
          return;
        }
        this.analysizeHost(strHost, special);
        this.analysizePath(strPath, special);
      } else {
        strHost = hostAndPath;
        strHost = this.analysizeStrHost(strHost);
        strHost = this.analysizeHostPath(strHost);
        this.analysizeHost(strHost, special);
      }
    } else if (input[0] == '/' && input[1] == '/') {
      let strOfPath = input.substring(1);
      this.analysizePath(strOfPath, false);
    } else {
      this.analysizeInfoPath(input);
    }
  }

  analysizeHostAndPath(input: string) {
    if (this.flags[1]) {
      let pos: number = 0;
      let inputLen: number = input.length;
      while (pos < inputLen) {
        if (input[pos] == '/' || input[pos] == '\\') {
          pos++;
          continue;
        }
        break;
      }
      input = input.substring(pos);
      if (input.length == 0) {
        this.flags[0] = 1;
        return;
      } else if (input.indexOf('/') != -1 || input.indexOf('\\') != -1) {
        this.parsingHostAndPath(input, pos);
      } else if (input.length != 0 && input.indexOf('/') == -1 && input.indexOf('\\') == -1) {
        this.analysizeOnlyHost(input, pos);
      }
    } else {
      let inputLen: number = input.length;
      if (inputLen > 0) {
        this.c_isSpecialPath = input[0] != '/' ? true : false;
      }
      this.analysizeNoDefaultProtocol(input);
    }
  }

  initOnlyInput(input: string): string {
    if (input.length == 0) {
      this.flags[0] = 1;
      return input;
    }
    let tempInput = input;
    if (tempInput.indexOf(':') != -1) {
      let pos = tempInput.indexOf(':');
      pos++;
      let scheme: string = tempInput.substring(0, pos);
      if (!this.analysizeScheme(scheme)) {
        return tempInput;
      }
      if (tempInput.indexOf('#') != -1) {
        let posTemp = tempInput.indexOf('#');
        let fragment: string = tempInput.substring(posTemp);
        this.analysizeFragment(fragment);
        tempInput = tempInput.substring(0, posTemp);
      }
      if (tempInput.indexOf('?') != -1) {
        let posTemp = tempInput.indexOf('?');
        let query: string = tempInput.substring(posTemp);
        this.analysizeQuery(query);
        tempInput = tempInput.substring(0, posTemp);
      }
      let restInput: string = tempInput.substring(pos);
      if (this.c_scheme == "file:") {
        this.analysizeFile(restInput);
      } else {
        this.analysizeHostAndPath(restInput);
      }
    } else {
      this.flags[0] = 1;
    }

    return tempInput;
  }

  analysizeInput(input: string): string {
    let pos: number = 0;
    if (input.indexOf('#') != -1) {
      pos = input.indexOf('#');
      let fragment: string = input.substring(pos);
      this.analysizeFragment(fragment);
      input = input.substring(0, pos);
    }
    if (input.indexOf('?') != -1) {
      pos = input.indexOf('?');
      let query: string = input.substring(pos);
      this.analysizeQuery(query);
      input = input.substring(0, pos);
    }
    let special: boolean = this.flags[1] ? true : false;
    let pathStr: string = input;
    this.analysizePath(pathStr, special);
    return input;
  }

  toolHasBase(input: string, strInput: string): string {
    if (input.length != 0 && input[0] == '/') {
      strInput = input.substring(1);
      return this.analysizeInput(strInput);
    } else if (input.length != 0 && input[0] != '/') {
      return this.analysizeInput(strInput);
    }
    return strInput;
  }

  parseSingleUrl(inputUrl: string): string {
    let trimedStr = deleteC0OrSpace(inputUrl);
    let processInput = deleteTabOrNewline(trimedStr);
    return this.initOnlyInput(processInput);
  }

  constructor() {
    preliminaryWork();
  }

  getHostName(): string {
    let temp = "";
    if (this.flags[4]) {
      if (!this.flags[10]) {
        temp = EncodePercentEncoding(this.c_host, URL_ENCODED_PERCENT_SIGN_CHARS);
      } else {
        temp = this.c_host;
      }
    }
    return temp;
  }

  setHostName(input: string) {
    if (this.flags[9]) {
      return;
    }
    let strHost: string = input;
    let len: number = strHost.length;
    for (let pos = 0; pos < len; pos++) {
      if ((strHost[pos] == ':') || (strHost[pos] == '?') || (strHost[pos] == '#') || (strHost[pos] == '/' || strHost[pos] == '\\')) {
        strHost = strHost.substring(0, pos);
        break;
      }
    }
    if (strHost.length == 0) {
      return;
    }
    let special: boolean = g_head.has(this.c_scheme);
    let tempUrl: innerURL = new innerURL();
    tempUrl.analysizeHost(strHost, special);
    if (tempUrl.flags[4]) {
      if (this.c_scheme == "file:" && (tempUrl.c_host == "localhost")) {
        tempUrl.c_host = "";
      }
      this.c_host = tempUrl.c_host;
      this.flags[4] = 1;
    }
  }

  setUserName(input: string) {
    if (input.length == 0) {
      this.c_username = "";
      this.flags[2] = 0;
    } else {
      let username: string = EncodePercentEncoding(input, USERINFO_PERCENT_SIGN_CHARS);
      let len: number = g_spesicalSymbols.length - 2;
      for (let i = 0; i <= len; i += 2) {
        // ReplaceSpecialSymbols
        username = username.replaceAll(g_spesicalSymbols[i], g_spesicalSymbols[i + 1]);
      }
      this.c_username = username;
      this.flags[2] = 1;
    }
  }

  setPassword(input: string) {
    if (input.length == 0) {
      this.c_password = "";
      this.flags[3] = 0;
    } else {
      let keyWord: string = EncodePercentEncoding(input, USERINFO_PERCENT_SIGN_CHARS);
      let len: number = g_spesicalSymbols.length - 2;
      for (let i = 0; i <= len; i += 2) {
        keyWord = keyWord.replaceAll(g_spesicalSymbols[i], g_spesicalSymbols[i + 1]);
      }
      this.c_password = keyWord;
      this.flags[3] = 1;
    }
  }

  setScheme(input: string) {
    let strInput: string = input;
    let special: boolean = g_head.has(this.c_scheme);
    let inputIsSpecial: boolean = g_head.has(input);
    if ((special != inputIsSpecial) || ((input == "file") && (this.flags[2] || this.flags[3] || this.flags[5]))) {
      return;
    }
    let tempUrl: innerURL = new innerURL();
    if (tempUrl.analysizeScheme(strInput)) {
      if (tempUrl.flags[1]) {
        this.flags[1] = 1;
      }
      this.c_scheme = tempUrl.c_scheme;
      for (const [key, value] of g_head) {
        if (key == this.c_scheme && value == this.c_port) {
          this.c_port = -1;
          this.flags[5] = 0;
        }
      }
    }
  }

  setFragment(input: string) {
    let temp: string = "";
    if (input.length == 0) {
      this.c_fragment = "";
      this.flags[8] = 0;
    } else {
      if (input[0] != '#') {
        temp = '#';
        temp += EncodePercentEncoding(input, FRAGMENT_PERCENT_SIGN_CHARS);
      } else {
        temp = EncodePercentEncoding(input, FRAGMENT_PERCENT_SIGN_CHARS);
      }
      this.analysizeFragment(temp);
    }
  }

  setSearch(input: string) {
    let temp: string = "";
    if (input.length == 0) {
      this.c_query = "";
      this.flags[7] = 0;
    } else {
      if (input[0] != '?') {
        temp = "?";
        temp += input;
      } else {
        temp = input;
      }
      let oldStr = "#";
      let newStr = "%23";
      temp = temp.replaceAll(oldStr, newStr);
      this.analysizeQuery(temp);
    }
  }

  setEncodeSearch(input: string) {
    this.setSearch(EncodePercentEncoding(input, QUERY_PERCENT_SIGN_CHARS));
  }

  setHost(input: string) {
    if (input.length == 0 || this.flags[9]) {
      return;
    }
    let strHost: string = input;
    let port: string = "";
    // splitString
    let strLen: number = input.length;
    for (let pos = 0; pos < strLen; pos++) {
      if ((input[pos] == ':') || (input[pos] == '?') || (input[pos] == '#') || (input[pos] == '/') || (input[pos] == '\\')) {
        strHost = input.substring(0, pos);
        if (input[pos] == ':') {
          pos++;
          port = input.substring(pos);
        }
        break;
      }
    }

    if (strHost.length == 0) {
      return;
    }
    let special: boolean = g_head.has(this.c_scheme);
    let tempUrl: innerURL = new innerURL();
    tempUrl.analysizeHost(strHost, special);
    if (tempUrl.flags[4]) {
      if (this.c_scheme == "file:" && tempUrl.c_host == "localhost") {
        tempUrl.c_host = "";
      }
      this.c_host = tempUrl.c_host;
      this.flags[4] = 1;
    } else {
      return;
    }
    if (port.length > 0) {
      let portLen: number = port.length;
      for (let pos = 0; pos < portLen; pos++) {
        if (port[pos] == '?' || port[pos] == '#' || port[pos] == '/' || port[pos] == '\\') {
          port = port.substring(0, pos);
          break;
        }
      }
      if (port.length > 0) {
        let tempPortUrl: innerURL = new innerURL();
        tempPortUrl.analysizePort(port);
        if (tempPortUrl.flags[5]) {
          this.flags[5] = 1;
          this.c_port = tempPortUrl.c_port;
        }
      }
    }
  }

  setPort(input: string) {
    let port: string = input;
    let portLen: number = port.length;
    for (let pos = 0; pos < portLen; pos++) {
      if ((port[pos] == '?') || (port[pos] == '#') || (port[pos] == '/') || (port[pos] == '\\')) {
        port = port.substring(0, pos);
        break;
      }
    }
    if (port.length > 0) {
      let tempUrl: innerURL = new innerURL();
      tempUrl.analysizePort(port);
      if (tempUrl.flags[5]) {
        this.flags[5] = 1;
        this.c_port = tempUrl.c_port;
      }
    }
  }

  setHref(input: string) {
    let str: string = input;
    str = deleteC0OrSpace(str);
    str = deleteTabOrNewline(str);
    let newUrl: innerURL = new innerURL();
    newUrl.initOnlyInput(str);
    if (!newUrl.flags[0]) {
      this.flags = newUrl.flags;
      this.c_fragment = newUrl.c_fragment;
      this.c_scheme = newUrl.c_scheme;
      this.c_query = newUrl.c_query;
      this.c_path = newUrl.c_path;
      this.c_host = newUrl.c_host;
      this.c_isSpecialPath = newUrl.c_isSpecialPath;
      this.c_username = newUrl.c_username;
      this.c_password = newUrl.c_password;
      this.c_port = newUrl.c_port;
    }
  }

  setPath(input: string) {
    let strPath: string = EncodePercentEncoding(input, PATH_PERCENT_SIGN_CHARS);
    if (this.flags[9] || strPath.length == 0) {
      return;
    }
    let oldStr: string = "%3A";
    let newStr: string = ":";
    strPath = strPath.replaceAll(oldStr, newStr);
    let special: boolean = g_head.has(this.c_scheme);
    if (this.c_scheme == "file:") {
      let tempUrl: innerURL = new innerURL();
      if ((strPath[0] == '/') || (strPath[0] == '\\' && this.flags[1])) {
        strPath = strPath.substring(1);
      }
      tempUrl.analysizeFilePath(strPath);
      if (tempUrl.flags[6]) {
        this.c_path = tempUrl.c_path;
        this.flags[6] = 1;
      }
    } else {
      let tempUrl: innerURL = new innerURL();
      if ((strPath[0] == '/') || (strPath[0] == '\\' && this.flags[1])) {
        strPath = strPath.substring(1);
      }
      tempUrl.analysizePath(strPath, special);
      if (tempUrl.flags[6]) {
        this.c_path = tempUrl.c_path;
        this.flags[6] = 1;
      }
    }
  }

  getSearch(): string {
    if (this.flags[7] && (this.c_query.length != 1)) {
      return this.c_query;
    }
    return "";
  }

  getEncodeSearch(): string {
    if (this.flags[7] && (this.c_query.length != 1)) {
      return EncodePercentEncoding(this.c_query, QUERY_PERCENT_SIGN_CHARS);
    }
    return "";
  }

  getUsername(): string {
    if (this.flags[2]) {
      return EncodePercentEncoding(this.c_username, USERINFO_PERCENT_SIGN_CHARS);
    }
    return "";
  }

  getPassword(): string {
    if (this.flags[3]) {
      return EncodePercentEncoding(this.c_password, USERINFO_PERCENT_SIGN_CHARS);
    }
    return "";
  }

  getFragment(): string {
    if (this.flags[8] && (this.c_fragment.length != 1)) {
      return EncodePercentEncoding(this.c_fragment, FRAGMENT_PERCENT_SIGN_CHARS);
    }
    return "";
  }

  getScheme(): string {
    if (this.c_scheme.length != 0) {
      return this.c_scheme;
    }
    return "";
  }

  getPath(): string {
    let temp: string = "/";
    if (this.c_isSpecialPath) {
      temp = "";
    }
    if (this.flags[6]) {
      let len: number = this.c_path.length;
      for (let i = 0; i < len; i++) {
        if (i < len - 1) {
          temp += this.c_path[i] + "/";
        } else {
          temp += this.c_path[i];
        }
      }
    } else {
      let special: boolean = g_head.has(this.c_scheme);
      if (!special) {
        temp = "";
      }
    }
    temp = EncodePercentEncoding(temp, PATH_PERCENT_SIGN_CHARS);
    return temp;
  }

  getPort(): string {
    if (this.flags[5]) {
      return String(this.c_port);
    }
    return "";
  }

  getHost(): string {
    let temp: string = this.c_host;
    if (this.flags[5]) {
      temp += ":";
      temp += String(this.c_port);
    }
    if (!this.flags[10]) {
      temp = EncodePercentEncoding(temp, URL_ENCODED_PERCENT_SIGN_CHARS);
    }
    return temp;
  }

  getIsIpv6(): boolean {
    return this.flags[10] ? true : false;
  }


}

function BaseIntoUrl(base: innerURL, res: innerURL, inputIsEmpty: boolean) {
  res.c_scheme = base.c_scheme;
  res.flags[1] = base.flags[1];

  res.c_host = base.c_host;
  res.flags[4] = 1;

  res.c_username = base.c_username;
  res.flags[2] = base.flags[2];

  res.c_password = base.c_password;
  res.flags[3] = base.flags[3];

  res.c_port = base.c_port;
  res.flags[5] = base.flags[5];

  if (inputIsEmpty) {
    res.c_path = base.c_path;
    res.flags[6] = base.flags[6];

    res.c_query = base.c_query;
    res.flags[7] = base.flags[7];

    res.c_fragment = base.c_fragment;
    res.flags[8] = base.flags[8];
  }
  res.flags[9] = base.flags[9];
  res.flags[10] = base.flags[10];
}

function shorteningPath(resUrl: innerURL, baseUrl: innerURL, isFile: boolean) {
  if (baseUrl.c_path.length == 0) {
    return;
  }
  if (resUrl.c_path.length == 1 && resUrl.c_path[0].length == 0) {
    resUrl.c_path.pop();
    return;
  }
  if ((baseUrl.c_path.length == 1) && isFile && isAlpha(baseUrl.c_path[0][0]) && (baseUrl.c_path[0][1] == ':')) {
    return;
  }
  baseUrl.c_path.pop();
}

function BasePathToString(base: innerURL): string {
  let temp: string = "";
  let len: number = base.c_path.length;
  for (let i = 0; i < len; i++) {
    if (i < len - 1) {
      temp += base.c_path[i] + "/";
    } else {
      temp += base.c_path[i];
    }
  }
  return temp;
}

function setEncodedData(dst: URL, src: innerURL) {
  dst.search_ = src.getEncodeSearch();
  dst.username_ = src.getUsername();
  dst.password_ = src.getPassword();
  dst.hostname_ = src.getHostName();
  dst.host_ = src.getHost();
  dst.hash_ = src.getFragment();
  dst.protocol_ = src.getScheme();
  dst.pathname_ = src.getPath();
  dst.port_ = src.getPort();
  dst.origin_ = src.getScheme() + '//' + src.getHost();
  dst.URLParamsClass_ = new URLParams(src.getEncodeSearch());
  dst.URLParamsClass_.parentUrl = dst;
  dst.setHref();
}

function containIllegalCode(str: string): Boolean {
  const unpairedSurrogateRe =
    /(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])/;
  const regex = new RegExp(unpairedSurrogateRe);
  return regex.test(str)
}

function fixIllegalString(str: string): string {
  if (containIllegalCode(str)) {
    return str.toWellFormed();
  } else {
    return str;
  }
}

export class URL {

  urlInner!: innerURL;

  href_: string = '';
  search_: string = '';
  origin_: string = '';
  username_: string = '';
  password_: string = '';
  hostname_: string = '';
  host_: string = '';
  hash_: string = '';
  protocol_: string = '';
  pathname_: string = '';
  port_: string = '';
  URLParamsClass_ !: URLParams;

  constructor() { }
  static parseURL(inputUrl: string, inputBase?: string | URL): URL {
    let nativeUrl!: URL;

    if (!inputBase) {
      nativeUrl = this.parseSingleInput(fixIllegalString(inputUrl));
    } else {
      if (typeof inputBase === 'string') {
        if (inputBase.length == 0) {
          throw new Error(`Parameter error. The type of ${inputBase} must be string`);
        }
        nativeUrl = this.parseBaseInput(fixIllegalString(inputUrl), fixIllegalString(inputBase));
      } else {
        nativeUrl = this.parseBaseUrl(fixIllegalString(inputUrl), inputBase);
      }
    }
    if (nativeUrl.urlInner.flags[0]) {
      let err: BusinessError = new BusinessError('Syntax Error. Invalid Url string');
      err.code = syntaxErrorCodeId;
      throw err;
    }
    setEncodedData(nativeUrl, nativeUrl.urlInner);
    return nativeUrl;
  }

  toString(): string {
    return this.href_;
  }

  get protocol(): string {
    return this.protocol_;
  }

  set protocol(scheme: string) {
    if (scheme.length == 0) {
      return;
    }
    if (this.protocol_ == "file:" && (this.host_ == "" || this.host_ == null)) {
      return;
    }
    scheme = deleteTabOrNewline(scheme)
    this.urlInner.setScheme(scheme);
    this.protocol_ = this.urlInner.getScheme();
    this.host_ = this.urlInner.getHost();
    this.port_ = this.urlInner.getPort();
    this.setHref();
  }

  get origin(): string {
    let kOpaqueOrigin: string = 'null';
    switch (this.protocol_) {
      case 'ftp:':
      case 'gopher:':
      case 'http:':
      case 'https:':
      case 'ws:':
      case 'wss:':
        return this.protocol_ + '//' + this.host_;
    }
    return kOpaqueOrigin;
  }

  get username(): string {
    return this.username_;
  }

  set username(input) {
    if (this.host_ === null || this.host_ === '' || this.protocol_ === 'file:') {
      return;
    }
    this.urlInner.setUserName(fixIllegalString(input));
    this.username_ = this.urlInner.getUsername();
    this.setHref();
  }

  get password(): string {
    return this.password_;
  }

  set password(input) {
    if (this.host_ === null || this.host_ === '' || this.protocol_ === 'file:') {
      return;
    }
    this.urlInner.setPassword(fixIllegalString(input));
    this.password_ = this.urlInner.getPassword();
    this.setHref();
  }

  get hash(): string {
    return this.hash_;
  }

  set hash(fragment) {
    fragment = deleteTabOrNewline(fragment)
    this.urlInner.setFragment(fixIllegalString(fragment));
    this.hash_ = this.urlInner.getFragment();
    this.setHref();
  }

  get search(): string {
    return this.search_;
  }

  set search(query) {
    query = deleteTabOrNewline(query)
    this.urlInner.setSearch(fixIllegalString(query));
    this.search_ = this.urlInner.getEncodeSearch();
    this.URLParamsClass_.updateParams(this.search_);  // todo
    this.setHref();
  }

  get hostname(): string {
    return this.hostname_;
  }

  set hostname(hostname) {
    hostname = deleteTabOrNewline(hostname)
    this.urlInner.setHostName(hostname);
    this.hostname_ = this.urlInner.getHostName();
    this.setHref();
  }

  get host(): string {
    return this.host_;
  }

  set host(host) {
    host = deleteTabOrNewline(host)
    this.urlInner.setHost(host);
    this.host_ = this.urlInner.getHost();
    this.hostname_ = this.urlInner.getHostName();
    this.port_ = this.urlInner.getPort();
    this.setHref();
  }

  get port(): string {
    return this.port_;
  }

  set port(port) {
    if (this.host_ === '' || this.protocol_ === 'file:' || port === '') {
      return;
    }
    port = deleteTabOrNewline(port)
    this.urlInner.setPort(port);
    this.port_ = this.urlInner.getPort();
    this.setHref();
  }

  get pathname(): string {
    return this.pathname_;
  }

  set pathname(path) {
    path = deleteTabOrNewline(path)
    this.urlInner.setPath(fixIllegalString(path));
    this.pathname_ = this.urlInner.getPath();
    this.setHref();
  }

  get params(): URLParams {
    return this.URLParamsClass_;
  }

  toJSON(): string {
    return this.href_;
  }

  get href(): string {
    return this.href_;
  }

  set href(href_) {
    this.urlInner.setHref(href_);
    if (this.urlInner.flags[0] != 1) {
      setEncodedData(this, this.urlInner);
    } else {
      let err: BusinessError = new BusinessError('Syntax Error. Invalid Url string');
      err.code = syntaxErrorCodeId;
      throw err;
    }
  }

  setHref(): void {
    let temp: string = this.protocol_;
    if (this.hostname_ != "") {
      temp += "//";
      if (this.password_ != "" || this.username_ != "") {
        if (this.username_ != "") {
          temp += this.username_;
        }
        if (this.password_ != "") {
          temp += ":";
          temp += this.password_;
        }
        temp += "@";
      }
      temp += this.hostname_;
      if (this.port_ != "") {
        temp += ":";
        temp += this.port_;
      }
    } else if (this.protocol_ == "file:") {
      temp += "//";
    }
    temp += this.pathname_;
    if (this.search_) {
      temp += this.search_;
    }
    if (this.hash_) {
      temp += this.hash_;
    }
    this.href_ = temp;
  }

  static parseSingleInput(input: string): URL {
    let result = new URL();
    let url = new innerURL();
    url.parseSingleUrl(input);
    result.urlInner = url;

    return result;
  }

  static parseBaseInput(input: string, base: string): URL {
    let result = new URL();
    let possibleBaseURL = new innerURL();
    let resUrl = new innerURL();
    let strInput = input;
    if (base.length == 0) {
      possibleBaseURL.flags[0] = 1;
    }
    possibleBaseURL.parseSingleUrl(base);
    if (possibleBaseURL.flags[0]) {
      resUrl.flags[0] = 1;
      result.urlInner = resUrl;
      return result;
    } else if (!possibleBaseURL.flags[0]) {
      strInput = resUrl.parseSingleUrl(strInput);
      if (!resUrl.flags[0]) {
        result.urlInner = resUrl;
        return result;
      }
      if ((input[0] == '/') && (input[1] == '/' || (input[1] == '\\' && possibleBaseURL.flags[1]))) {
        let newInput: string = possibleBaseURL.c_scheme + input;
        resUrl.flags[0] = 0;
        resUrl.parseSingleUrl(newInput);
        result.urlInner = resUrl;
        return result;
      }
      if (!possibleBaseURL.flags[9]) {
        resUrl.flags[0] = 0;
        BaseIntoUrl(possibleBaseURL, resUrl, input.length == 0);
        strInput = resUrl.toolHasBase(input, strInput);
        if (input.length != 0 && input[0] != '/' && resUrl.c_path.length == 0) {
          resUrl.c_path = possibleBaseURL.c_path;
          resUrl.flags[6] = possibleBaseURL.flags[6];
        }
        if (input.length != 0 && input[0] != '/' && resUrl.c_path.length != 0) {
          let isFile: boolean = ((resUrl.c_scheme == "file:") ? true : false);
          shorteningPath(resUrl, possibleBaseURL, isFile);
          let basePathStr: string = BasePathToString(possibleBaseURL);
          basePathStr == "" ? basePathStr = strInput : basePathStr += "/" + strInput;
          resUrl.c_path = [];
          resUrl.analysizeInput(basePathStr);
          resUrl.flags[6] = 1;
        }
      } else if (possibleBaseURL.flags[9]) {
        resUrl.flags[0] = 1;
        result.urlInner = resUrl;
        return result;
      }
    }
    result.urlInner = resUrl;
    return result;
  }

  static parseBaseUrl(input: string, baseUrl: URL): URL {
    let base: innerURL = baseUrl.urlInner;
    let result: URL = new URL();
    let resUrl: innerURL = new innerURL();
    let strInput: string = input;
    resUrl.parseSingleUrl(strInput);
    if (!resUrl.flags[0]) {
      result.urlInner = resUrl;
      return result;
    }
    if (input.length != 0) {
      if ((input[0] == '/') && (input[1] == '/' || (input[1] == '\\' && base.flags[1]))) {
        let newInput: string = base.c_scheme + input;
        resUrl.flags[0] = 0;
        resUrl.initOnlyInput(newInput);
        result.urlInner = resUrl;
        return result;
      }
    }
    if (!base.flags[9]) {
      resUrl.flags[0] = 0;
      BaseIntoUrl(base, resUrl, input.length == 0);
      strInput = resUrl.toolHasBase(input, strInput);
      if (input.length != 0 && input[0] != '/' && resUrl.c_path.length == 0) {
        resUrl.c_path = base.c_path;
        resUrl.flags[6] = base.flags[6];
      }
      if (input.length != 0 && input[0] != '/' && resUrl.c_path.length != 0) {
        let isFile: boolean = ((resUrl.c_scheme == "file:") ? true : false);
        shorteningPath(resUrl, base, isFile);
        let basePathStr: string = BasePathToString(base);
        basePathStr == "" ? basePathStr = strInput : basePathStr += "/" + strInput;
        resUrl.c_path = [];
        resUrl.analysizeInput(basePathStr);
        resUrl.flags[6] = 1;
      }
    } else if (base.flags[9]) {
      resUrl.flags[0] = 1;
      result.urlInner = resUrl;
      return result;
    }
    result.urlInner = resUrl;
    return result;
  }
}

function removeKeyValuePairs(str: string, key: string): string {
  const regex = new RegExp(`\\b${key}=[^&]*&?`, 'g');
  let result = str.replace(regex, '');
  if (result.endsWith('&')) {
    result = result.slice(0, -1);
  }
  return result;
}

type UrlParamKey = 'name';

export class URLParams {
  urlClass: NativeURLSearchParams;
  parentUrl: URL | null = null;
  constructor(input?: [string, string][] | Record<UrlParamKey, string> | string | URLParams | object) {
    let arr = parameterProcess(input);
    this.urlClass = new NativeURLSearchParams();
    this.urlClass.setArray(arr);
  }

  append(params1: string, params2: string): void {
    params1 = fixUSVstring(params1);
    params2 = fixUSVstring(params2);
    this.urlClass.append(params1, params2);
    if (this.parentUrl != null) {
      this.parentUrl.urlInner.setSearch(this.toString());
      this.parentUrl.search_ = this.parentUrl.urlInner.getSearch();
      this.parentUrl.setHref();
    }
  }
  set(setName: string, setValues: string): void {
    setName = fixUSVstring(setName);
    setValues = fixUSVstring(setValues);
    this.urlClass.set(setName, setValues);
    if (this.parentUrl != null) {
      this.parentUrl.urlInner.setSearch(this.toString());
      this.parentUrl.search_ = this.parentUrl.urlInner.getSearch();
      this.parentUrl.setHref();
    }
  }
  sort(): void {
    this.urlClass.sort();
    if (this.parentUrl != null) {
      this.parentUrl.urlInner.setSearch(this.toString());
      this.parentUrl.search_ = this.parentUrl.urlInner.getSearch();
      this.parentUrl.setHref();
    }
  }
  has(hasname: string): boolean {
    hasname = fixUSVstring(hasname);
    return this.urlClass.isHas(hasname);
  }
  toString(): string {
    return this.urlClass.toString();
  }

  keys(): CIteratorKeysOrValues {
    return new CIteratorKeysOrValues(this, 'keys');
  }

  values(): CIteratorKeysOrValues {
    return new CIteratorKeysOrValues(this, 'values');
  }

  getAll(getAllname: string): Array<string> {
    getAllname = fixUSVstring(getAllname);
    return this.urlClass.getAll(getAllname);
  }

  get(getname: string): string | undefined {
    getname = fixUSVstring(getname);
    return this.urlClass.get(getname);
  }

  entries(): CIterator {
    return new CIterator(this);
  }

  delete(deleteName: string): void {
    deleteName = fixUSVstring(deleteName);
    this.urlClass.delete(deleteName);
    if (this.parentUrl != null) {
      let searchStr: string = removeKeyValuePairs(this.parentUrl.urlInner.getSearch(), deleteName);
      this.parentUrl.urlInner.setSearch(searchStr);
      this.parentUrl.search_ = searchStr;
      this.parentUrl.setHref();
    }
  }

  forEach(
    objfun: (value: string, key: string, param: URLParams) => void,
    thisArg?: Object
  ) {
    let array = this.urlClass.getArray();
    if (array.length == 0) {
      return;
    }
    let size = array.length - 1;
    for (let i = 0; i < size; i += 2) { // 2:Searching for the number and number of keys and values 2
      let key = array[i];
      let value = array[i + 1];
      objfun(value, key, this);
    }

  }

  [Symbol.iterator](): CIterator {
    return new CIterator(this);
  }

  updateParams(input: string): void {
    let arr = parameterProcess(input);
    this.urlClass.setArray(arr);
  }
}

export class NativeURLSearchParams {
  private searchParams!: Array<string>;
  constructor(input?: string) {
    if (!input) {
      this.setArray(new Array());
    }

  }

  // void URLSearchParams::HandleIllegalChar(std::wstring& inputStr, std::wstring::const_iterator it)

  get(key: string): string | undefined {
    let len = this.searchParams.length;
    for (let i = 0; i < len; i += 2) {
      if (key == this.searchParams[i]) {
        return this.searchParams[i + 1];
      }
    }
    return undefined;
  }

  getAll(key: string): Array<string> {
    let result = new Array<string>();
    let len = this.searchParams.length;
    for (let i = 0; i < len; i += 2) {
      if (key == this.searchParams[i]) {
        result.push(this.searchParams[i + 1]);
      }
    }
    return result;
  }

  append(key: string, value: string) {
    this.searchParams.push(key);
    this.searchParams.push(value);
  }
  delete(name: string) {
    for (let i = this.searchParams.length - 2; i >= 0; i -= 2) {
      if (this.searchParams[i] == name) {
        this.searchParams.splice(i, 2);
      }
    }
  }
  entries(): Array<Array<string>> {
    let result = new Array<Array<string>>();

    for (let i = 0; i < this.searchParams.length; i += 2) {
      let arr = new Array<string>();
      arr.push(this.searchParams[i]);
      arr.push(this.searchParams[i + 1]);
      result.push(arr);
    }
    return result;

  }
  isHas(str: string): boolean {//key
    let len = this.searchParams.length;
    for (let i = 0; i < len; i += 2) {
      if (this.searchParams[i] == str) {
        return true;
      }
    }
    return false;

  }
  set(key: string, value: string) {
    let len = this.searchParams.length;
    let flag = false;
    for (let i = 0; i < len;) {
      if (this.searchParams[i] == key) {
        if (!flag) {
          this.searchParams[i + 1] = value;
          flag = true;
          i += 2;

        } else {
          this.searchParams.splice(i, 2);
        }
      } else {
        i += 2;
      }
    }
    if (!flag) {
      this.append(key, value);
    }
  }
  sort() {
    let len = this.searchParams.length;
    if (len <= 2 || (len % 2 != 0)) { // 2: Iterate over key-value pairs
      return;
    }
    let i = 0;
    for (; i < len - 2; i += 2) { // 2:Iterate over key-value pairs
      let j = i + 2; // 2:Iterate over key-value pairs
      for (; j < len; j += 2) { // 2:Iterate over key-value pairs
        let tmp: boolean = (this.searchParams[i] > this.searchParams[j]);
        if (tmp) {
          let curKey = this.searchParams[i];
          let curVal = this.searchParams[i + 1];
          this.searchParams[i] = this.searchParams[j];
          this.searchParams[i + 1] = this.searchParams[j + 1];
          this.searchParams[j] = curKey;
          this.searchParams[j + 1] = curVal;
        }
      }
    }
  }


  iterByKeys(): Array<string> {
    let res = new Array<string>();
    let len = this.searchParams.length;
    for (let i = 0; i < len; i += 2) {
      res.push(this.searchParams[i]);
    }
    return res;
  }
  getArray(): Array<string> {
    return this.searchParams;
  }
  setArray(arr: Array<string>) {
    this.searchParams = arr;
  }
  iterByValues(): Array<string> {
    let res = new Array<string>();
    let len = this.searchParams.length;
    for (let i = 1; i < len; i += 2) {
      res.push(this.searchParams[i]);
    }
    return res;
  }
  toString(): string {
    const resultArray: string[] = [];
    let array = this.getArray();
    let arrayLen: number = array.length;
    let key: string = '';
    let value: string = '';
    let reg = new RegExp('=', 'g');
    for (let pos: number = 0; pos < arrayLen; pos += 2) { // 2:Even subscripts exist as key values
      key = EncodePercentEncoding(array[pos], PARAMS_TOSTRING_SIGN_CHARS).replace(/%20/g, '+');
      value = EncodePercentEncoding(array[pos + 1], PARAMS_TOSTRING_SIGN_CHARS).replace(/%20/g, '+');
      resultArray.push(`${pos > 0 ? '&' : ''}${key}=${value}`);
    }
    return resultArray.join('');
  }
}
let seachParamsArr = new Array<string>();

function parameterProcess(input?: [string, string][] | Record<string, string> | string | URLParams | object): Array<string> {

  if (input == undefined || input == null || input == '') {
    seachParamsArr = new Array<string>();;
    return seachParamsArr;
  } else if (typeof input == 'string') {
    return initToStringSeachParams(fixUSVstring(input as string));
  } else if (input instanceof URLParams) {
    return input.urlClass.getArray();
    // } else if (typeof input === 'object' || typeof input === 'function') {
  } else {
    return sysObjectParams(input);
  }
}

function sysObjectParams(input: object | Iterable<[]>): Array<string> {
  if (typeof input[Symbol.iterator] === 'function') {
    return iteratorMethodThrow(input as Iterable<[string]>);
  }
  return recordMethod(input);
}

function recordMethod(input: object): Array<string> {
  const keys = Reflect.ownKeys(input);
  seachParamsArr = [];
  for (let i = 0; i <= keys.length; i++) {
    const key = keys[i];
    const desc = Reflect.getOwnPropertyDescriptor(input, key);
    if (desc !== undefined && desc.enumerable) {
      const typedKey = toHleString(key);
      const typedValue = toHleString(input[key]);
      seachParamsArr.push(typedKey, typedValue);
    }
  }
  return seachParamsArr;
}

function iteratorMethodThrow(input: Iterable<[string]>): Array<string> {
  let pairs = new Array<Array<string>>();;
  seachParamsArr = [];
  for (const pair of input) {
    if ((typeof pair !== 'object' && typeof pair !== 'function') || pair === null || typeof pair[Symbol.iterator] !== 'function') {
      throw new BusinessError(`Parameter error. The type of ${input} must be string[][]`);
    }
    const convertedPair = new Array<string>();;
    for (let element of pair) {
      convertedPair.push(element);
    }
    pairs.push(convertedPair);
  }

  for (const pair of pairs) {
    if (pair.length !== 2) { // 2:Searching for the number and number of keys and values 2
      throw new BusinessError(`Parameter error. The type of ${input} must be string[][]`);
    }
    seachParamsArr.push(pair[0], pair[1]);
  }
  return seachParamsArr;
}

function toHleString(arg: string | symbol | number): string {
  return arg.toString();
}

function processInputString2D(input: [string, string][]): Array<string> {
  let result = new Array<string>();
  for (let it of input) {
    result.push(fixUSVstring(it[0]));
    result.push(fixUSVstring(it[1]));
  }
  return result;
}

function initToStringSeachParams(input: string): Array<string> {
  if (input[0] == '?') {
    input = input.slice(1);
  }
  let strVal = input.replaceAll('+', ' ');
  seachParamsArr = stringParmas(strVal);
  return seachParamsArr.map(item => item = decodeStringParmas(item));
}

const unhexTable = new Int8Array([
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 0 - 15
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 16 - 31
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 32 - 47
  +0, +1, +2, +3, +4, +5, +6, +7, +8, +9, -1, -1, -1, -1, -1, -1, // 48 - 63
  -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 64 - 79
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 80 - 95
  -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 96 - 111
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 112 - 127
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, // 128 ...
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
  -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,  // ... 255
]);

function utf8Decode(uint8Array: Uint8Array) {
  let result: string = '';
  let i = 0;
  while (i < uint8Array.length) {
    const byte1 = uint8Array[i];
    if ((byte1 & 0x80) === 0) {
      // 单字节字符 (0xxxxxxx)
      result += String.fromCharCode(byte1);
      i++;
    } else if ((byte1 & 0xE0) === 0xC0) {
      if (i + 1 >= uint8Array.length) {
        result += String.fromCharCode(0XFFFD);
        i++;
        continue;
      }
      // 双字节字符 (110xxxxx 10xxxxxx)
      const byte2 = uint8Array[i + 1];
      const codePoint = ((byte1 & 0x1F) << 6) | (byte2 & 0x3F);
      result += String.fromCharCode(codePoint);
      i += 2;
    } else if ((byte1 & 0xF0) === 0xE0) {
      if (i + 2 >= uint8Array.length) {
        result += String.fromCharCode(0XFFFD);
        i++;
        continue;
      }
      // 三字节字符 (1110xxxx 10xxxxxx 10xxxxxx)
      const byte2 = uint8Array[i + 1];
      const byte3 = uint8Array[i + 2];
      const codePoint = ((byte1 & 0x0F) << 12) | ((byte2 & 0x3F) << 6) | (byte3 & 0x3F);
      result += String.fromCharCode(codePoint);
      i += 3;
    } else if ((byte1 & 0xF8) === 0xF0) {
      // 四字节字符 (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx)
      if (i + 3 >= uint8Array.length) {
        result += String.fromCharCode(0XFFFD);
        i++;
        continue;
      }

      const byte2 = uint8Array[i + 1];
      const byte3 = uint8Array[i + 2];
      const byte4 = uint8Array[i + 3];
      const codePoint = ((byte1 & 0x07) << 18) | ((byte2 & 0x3F) << 12) | ((byte3 & 0x3F) << 6) | (byte4 & 0x3F);
      result += String.fromCodePoint(codePoint);
      i += 4;
    } else {
      result += String.fromCharCode(0XFFFD);
      i++;
    }
  }
  return result;
}

function unescapeBuffer(s: string): Uint8Array {
  const out: Uint8Array = new Uint8Array(s.length);
  let index = 0;
  let outIndex = 0;
  let currentChar;
  let nextChar;
  let hexHigh;
  let hexLow;
  const maxLength = s.length - 2;
  // Flag to know if some hex chars have been decoded
  let hasHex = false;
  while (index < s.length) {
    currentChar = s.charCodeAt(index);
    if (currentChar === 43) { // '+'
      out[outIndex++] = 32; // ' '
      index++;
      continue;
    }
    if (currentChar === 37 /* '%' */ && index < maxLength) {
      currentChar = s.charCodeAt(++index);
      hexHigh = unhexTable[currentChar];
      if (!(hexHigh >= 0)) {
        out[outIndex++] = 37; // '%'
        continue;
      } else {
        nextChar = s.charCodeAt(++index);
        hexLow = unhexTable[nextChar];
        if (!(hexLow >= 0)) {
          out[outIndex++] = 37; // '%'
          index--;
        } else {
          hasHex = true;
          currentChar = hexHigh * 16 + hexLow;
        }
      }
    }
    out[outIndex++] = currentChar;
    index++;
  }
  return hasHex ? out.slice(0, outIndex) : out;
}

function decodeStringParmas(input: string): string {
  let strVal = '';
  try {
    strVal = decodeURIComponent(input);
  } catch (e) {
    strVal = utf8Decode(unescapeBuffer(input));
  }
  return strVal;
}

function decodeSafelyOut(input: string): string {
  let decodedString: string = '';
  let decodedTemp: string = '';
  let index: number = 0;
  while (index < input.length) {
    if (input[index] === '%' && new RegExp('[0-9A-Fa-f]{2}').test(input.slice(index + 1, index + 3))) {
      const encodedChar = input.slice(index, index + 3);
      try {
        decodedString += decodeURIComponent(decodedTemp + encodedChar);
        decodedTemp = '';
      } catch (e) {
        decodedTemp += encodedChar;
      }
      index += 3;
      continue;
    }
    if (decodedTemp == '') {
      decodedString += input[index];
    } else {
      decodedString += decodedTemp;
      decodedString += input[index];
      decodedTemp = '';
    }
    index++;
  }
  return decodedTemp == '' ? decodedString : decodedString += decodedTemp;
}

function stringParmas(stringParm: string): Array<string> {
  let seachParasVec = new Array<string>();
  let strStartPos = 0;
  let strLastPos = 0;
  let isHasSpace = false;
  let buf = "";
  let iteaor = 0;
  for (iteaor = 0; iteaor < stringParm.length; iteaor++) {
    let code = stringParm[iteaor];
    switch (code) {
      case '&':
        {
          if (strStartPos == iteaor) {
            strLastPos = iteaor + 1;
            strStartPos = iteaor + 1;
            continue;
          }
          if (strLastPos < iteaor) {
            buf += stringParm.substr(strLastPos, iteaor - strLastPos);
          }
          seachParasVec.push(buf);
          if (!isHasSpace) {
            seachParasVec.push("");
          }
          isHasSpace = false;
          buf = "";
          strLastPos = iteaor + 1;
          strStartPos = iteaor + 1;
          break;

        }
      case '=':
        {
          if (isHasSpace) {
            break;
          }
          if (strLastPos < iteaor) {
            buf += stringParm.substr(strLastPos, iteaor - strLastPos);
          }
          seachParasVec.push(buf);
          buf = "";
          strLastPos = iteaor + 1;
          isHasSpace = true;
          break;
        }
      default: break;
    }
  }
  if (strStartPos == iteaor) {
    return seachParasVec;
  }
  if (strLastPos < iteaor) {
    buf += stringParm.substr(strLastPos, iteaor - strLastPos);
  }
  seachParasVec.push(buf);
  if (!isHasSpace) {
    seachParasVec.push("");
  }

  return seachParasVec;
}

const unpairedSurrogateRe: RegExp =
  /(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])/;

function fixUSVstring(str: string) {
  const match = unpairedSurrogateRe.exec(str);
  if (!match) {
    return str;
  }
  return str.toWellFormed();
}

class CIterator implements Iterator<Array<string>> {
  entries: Array<Array<string>>;
  index = 0;
  base: URLParams;
  constructor(base: URLParams) {
    this.base = base;
    this.entries = base.urlClass.entries();
  }
  next(): IteratorResult<Array<string>> {
    if (this.index < this.entries.length) {
      return { value: this.entries[this.index++], done: false };
    } else {
      return { value: null, done: true };
    }
  }

  [Symbol.iterator](): Iterator<Array<string>> {
    return this;
  }
}

class CIteratorKeysOrValues implements Iterator<string> {
  iterval: Array<string>;
  index = 0;
  base: URLParams;
  constructor(base: URLParams, kind: string) {
    this.base = base;
    if (kind === 'keys') {
      this.iterval = base.urlClass.iterByKeys();
    } else {
      this.iterval = base.urlClass.iterByValues();
    }
  }
  next(): IteratorResult<string> {
    if (this.index < this.iterval.length) {
      return { value: this.iterval[this.index++], done: false };
    } else {
      return { value: null, done: true };
    }
  }

  [Symbol.iterator](): Iterator<string> {
    return this;
  }
}

const hexTable: Array<string> = new Array<string>(256);
for (let i = 0; i < 256; ++i) {
  hexTable[i] = '%' +
    ((i < 16 ? '0' : '') + i.toString(16))  // 转换为十六进制并处理前导零
      .toUpperCase();  // 转换为大写
}

// hostname, host
const URL_ENCODED_PERCENT_SIGN_CHARS = new Int8Array([
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00 - 0x0F
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10 - 0x1F
  0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20 - 0x2F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, // 0x30 - 0x3F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 0x4F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 0x50 - 0x5F
  0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, // 0x70 - 0x7F
]);

// username, password {'%25':'%'}
const USERINFO_PERCENT_SIGN_CHARS = new Int8Array([
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00 - 0x0F
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10 - 0x1F
  0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20 - 0x2F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, // 0x30 - 0x3F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 0x4F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 0x50 - 0x5F
  0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, // 0x70 - 0x7F
]);

// path {'%7C': '|', '%5B': '[', '%5D': ']', '%25': '%'} %5C '\\'
const PATH_PERCENT_SIGN_CHARS = new Int8Array([
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00 - 0x0F
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10 - 0x1F
  0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20 - 0x2F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, // 0x30 - 0x3F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 0x4F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x50 - 0x5F  // todo 5c
  0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, // 0x70 - 0x7F
]);

// hash, fragment {'%7C': '|', '%5B': '[', '%5D': ']', '%7B': '{', '%7D': '}', '%60': '`', '%25': '%'}
const FRAGMENT_PERCENT_SIGN_CHARS = new Int8Array([
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00 - 0x0F
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10 - 0x1F
  0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20 - 0x2F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, // 0x30 - 0x3F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 0x4F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, // 0x50 - 0x5F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 0x70 - 0x7F
]);

// query {'%7C': '|', '%5B': '[', '%5D': ']', '%7B': '{', '%7D': '}', '%60': '`', '%5E': '^', '%25': '%'}
const QUERY_PERCENT_SIGN_CHARS = new Int8Array([
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00 - 0x0F
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10 - 0x1F
  0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, // 0x20 - 0x2F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, // 0x30 - 0x3F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 0x4F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x50 - 0x5F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 0x70 - 0x7F
]);

// params tostring encode
const PARAMS_TOSTRING_SIGN_CHARS = new Int8Array([
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00 - 0x0F
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10 - 0x1F
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, // 0x20 - 0x2F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 0x30 - 0x3F
  0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 0x4F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 0x50 - 0x5F
  0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6F
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, // 0x70 - 0x7F
]);

function EncodePercentEncoding(str: string, noEscapeTable: Int8Array) {
  const len = str.length;
  if (len === 0)
    return '';

  let outVal = '';
  let lastPos = 0;
  let i = 0;

  outer:
  for (; i < len; i++) {
    let c = str.charCodeAt(i);

    // ASCII
    while (c < 0x80) {
      if (noEscapeTable[c] !== 1) {
        if (lastPos < i)
          outVal += str.slice(lastPos, i);
        lastPos = i + 1;
        outVal += hexTable[c];
      }

      if (++i === len)
        break outer;

      c = str.charCodeAt(i);
    }

    if (lastPos < i)
      outVal += str.slice(lastPos, i);

    // Multi-byte characters ...
    if (c < 0x800) {
      lastPos = i + 1;
      outVal += hexTable[0xC0 | (c >> 6)] +
        hexTable[0x80 | (c & 0x3F)];
      continue;
    }
    if (c < 0xD800 || c >= 0xE000) {
      lastPos = i + 1;
      outVal += hexTable[0xE0 | (c >> 12)] +
        hexTable[0x80 | ((c >> 6) & 0x3F)] +
        hexTable[0x80 | (c & 0x3F)];
      continue;
    }
    // Surrogate pair
    ++i;

    // This branch should never happen because all URLSearchParams entries
    // should already be converted to USVString. But, included for
    // completion's sake anyway.
    if (i >= len)
      console.log('errerrerrerr');

    const c2 = str.charCodeAt(i) & 0x3FF;

    lastPos = i + 1;
    c = 0x10000 + (((c & 0x3FF) << 10) | c2);
    outVal += hexTable[0xF0 | (c >> 18)] +
      hexTable[0x80 | ((c >> 12) & 0x3F)] +
      hexTable[0x80 | ((c >> 6) & 0x3F)] +
      hexTable[0x80 | (c & 0x3F)];
  }
  if (lastPos === 0)
    return str;
  if (lastPos < len)
    return outVal + str.slice(lastPos);
  return outVal;
}